﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Hiro;
using Hiro.Containers;

namespace EfficientlyLazy.PhotoFramer.Service
{
    public class ServiceFactory
    {
        private static List<IRegistration> _registrations;
        private static IMicroContainer _container;
        private static DependencyMap _dependancyMap;
        private static bool _needsCompile;

        static ServiceFactory()
        {
            Clear();
        }

        public static IAssemblyRegistration WithAssembly(Assembly assembly)
        {
            _needsCompile = true;

            var assmReg = new AssemblyRegistration(assembly);

            _registrations.Add(assmReg);

            return assmReg;
        }

        public static IAssemblyRegistration WithAssembly(string assemblyString)
        {
            _needsCompile = true;

            var assembly = Assembly.Load(assemblyString);

            var assmReg = new AssemblyRegistration(assembly);

            _registrations.Add(assmReg);

            return assmReg;
        }

        public static ITypeRegistration Register(Type type)
        {
            _needsCompile = true;

            var typeReg = new TypeRegistration(type);

            _registrations.Add(typeReg);

            return typeReg;
        }

        public static ITypeRegistration Register<T>()
        {
            _needsCompile = true;

            var typeReg = new TypeRegistration(typeof(T));

            _registrations.Add(typeReg);

            return typeReg;
        }

        public static void Register<TInterface, TService>() where TService : TInterface
        {
            _needsCompile = true;

            _registrations.Add(new SimpleRegistration(typeof(TInterface), typeof(TService)));
        }

        public static void Register(Type intrface, Type service)
        {
            _needsCompile = true;

            _registrations.Add(new SimpleRegistration(intrface, service));
        }

        public static void Register<T>(T service)
        {
            _needsCompile = true;

            _registrations.Add(new SimpleRegistration<T>(service));
        }

        public static void Clear()
        {
            _registrations = new List<IRegistration>();
            _dependancyMap = null;
            _container = null;
            _needsCompile = true;
        }

        public static T Create<T>() where T : class
        {
            return Create<T>(null);
        }

        public static T Create<T>(string serviceName) where T : class
        {
            if (_needsCompile || _dependancyMap == null || _container == null)
            {
                _dependancyMap = new DependencyMap();

                _registrations.Where(x => x.IsCompiled).ToList().ForEach(x => x.AddRegistrations());

                _container = _dependancyMap.CreateContainer();

                _registrations.Where(x => !x.IsCompiled).ToList().ForEach(x => x.AddRegistrations());

                _needsCompile = false;
            }

            var retVal = _container.GetInstance<T>(serviceName);

            if (retVal == null)
            {
                var targetContainer = _container.NextContainer;

                while (targetContainer != null)
                {
                    retVal = targetContainer.GetInstance<T>(serviceName);

                    if (retVal != null)
                    {
                        return retVal;
                    }

                    targetContainer = targetContainer.NextContainer;
                }
            }

            return retVal;
        }

        private interface IRegistration
        {
            bool IsCompiled { get; }
            void AddRegistrations();
        }

        private class SimpleRegistration : IRegistration
        {
            private readonly Type _interface;
            private readonly Type _service;

            public bool IsCompiled { get { return true; } }

            public SimpleRegistration(Type intrface, Type service)
            {
                _interface = intrface;
                _service = service;
            }

            public void AddRegistrations()
            {
                _dependancyMap.AddService(_interface, _service);
            }
        }

        private class SimpleRegistration<T> : IRegistration
        {
            private readonly T _service;

            public bool IsCompiled { get { return false; } }

            public SimpleRegistration(T service)
            {
                _service = service;
            }

            public void AddRegistrations()
            {
                _container.AddService(_service);
            }
        }

        public interface IBinding
        {
            void Bind(Type intrface, Type service);
        }

        private class Binding : IBinding
        {
            private readonly AssemblyRegistration _assemblyRegistration;

            public Binding(AssemblyRegistration assemblyRegistration)
            {
                _assemblyRegistration = assemblyRegistration;
            }

            public void Bind(Type intrface, Type service)
            {
                _assemblyRegistration.BindingInterface = intrface;
                _assemblyRegistration.BindingService = service;
            }
        }

        public interface ITypeRegistration
        {
            void Bind(Type intrface, Type service);
        }

        private class TypeRegistration : ITypeRegistration, IRegistration
        {
            private readonly Type _type;
            private Type _bindingInterface;
            private Type _bindingService;

            public bool IsCompiled { get { return true; } }

            public TypeRegistration(Type type)
            {
                _type = type;
            }

            public void Bind(Type intrface, Type service)
            {
                _bindingInterface = intrface;
                _bindingService = service;
            }

            public void AddRegistrations()
            {
                var specificInterface = _bindingInterface.MakeGenericType(_type);
                var specificClass = _bindingService.MakeGenericType(_type);

                _dependancyMap.AddService(specificInterface, specificClass);
            }
        }

        public interface IAssemblyRegistration
        {
            IBinding RegisterByInterface<T>();
            IBinding RegisterByAttribute<T>() where T : Attribute;
        }

        private class AssemblyRegistration : IAssemblyRegistration, IRegistration
        {
            private readonly Assembly _assembly;
            private Type _byAttribute;
            private Type _byInterface;
            internal Type BindingInterface;
            internal Type BindingService;

            public bool IsCompiled { get { return true; } }

            public AssemblyRegistration(Assembly assembly)
            {
                _assembly = assembly;
            }

            public IBinding RegisterByInterface<T>()
            {
                _byInterface = typeof(T);

                return new Binding(this);
            }

            public IBinding RegisterByAttribute<T>() where T : Attribute
            {
                _byAttribute = typeof(T);

                return new Binding(this);
            }

            public void AddRegistrations()
            {
                List<Type> types;

                if (_byAttribute != null)
                {
                    types = _assembly.GetTypes()
                        .Where(x => x.IsClass && !x.IsAbstract && !x.IsNestedPrivate && !x.IsGenericType)
                        .Where(x => x.GetCustomAttributes(true).Where(i => i.GetType() == _byAttribute).Any())
                        .ToList();
                }
                else if (_byInterface != null && _byInterface.IsInterface)
                {
                    types = _assembly.GetTypes()
                        .Where(x => x.IsClass && !x.IsAbstract && !x.IsNestedPrivate && !x.IsGenericType)
                        .Where(x => x.GetInterfaces().Where(i => i == _byInterface).Any())
                        .ToList();
                }
                else if (_byInterface != null && !_byInterface.IsInterface)
                {
                    throw new Exception();
                }
                else
                {
                    types = _assembly.GetTypes()
                        .Where(x => x.IsClass && !x.IsAbstract && !x.IsNestedPrivate && !x.IsGenericType)
                        .ToList();
                }

                if (BindingInterface == null || BindingService == null)
                {
                    foreach (var type in types)
                    {
                        var interfaces = type.GetInterfaces();

                        foreach (var intrface in interfaces)
                        {
                            _dependancyMap.AddService(intrface, type);
                        }
                    }
                }
                else
                {
                    foreach (var type in types)
                    {
                        var specificInterface = BindingInterface.MakeGenericType(type);
                        var specificClass = BindingService.MakeGenericType(type);

                        _dependancyMap.AddService(specificInterface, specificClass);
                    }
                }
            }
        }
    }
}